import { requestSDspotMarketZcq } from "@src/api/fetch"
import { getSpotMartetZcqReqInfo } from "@src/entries/options/generate"
import { ZpBackParams, ZpParams, ZpSocketParams } from "./type"
import { encrypt, getDataType, hasValue, promisetTimeOut, validateAndParseJSON } from "@src/utils/common"
import { throttle } from 'lodash'
import { yunNao } from "@src/api/yunNao"
import { yunNaoAccount } from "@src/dataFormatConversion/constant"
import { priviateAccount, userUnitid } from "@src/config/accountInfo"
import { ImageVerification } from "./ImageVerification"
import { AsyncQueue } from "@src/utils/AsyncQueue"
import { getOneGpxxData } from "."


/**
 * 日滚动撮合的socket服务端
 * DAY_ROLL_CRAWLER("day_roll_crawler", "日内滚动-爬虫")
 * DELIST_CRAWLER("delist_crawler", "挂摘牌-爬虫")
 * */
export const ZgpSocketUrl:string = import.meta.env.VITE_APP_YUN_NAO_BASE_WS + 'app/etrmws/websocket/crawler?code=delist_crawler'
// export const ZgpSocketUrl:string = 'wss://4f9b-14-23-59-234.ngrok-free.app?'

const delayConfig = {
  checkDelay: 20,
  sellBuyDelay: 20
}

class UniqueSocket {
  private static ws:WebSocket // socket 实例
  private static api:string // socket 服务端地址

  private socketName:string // 记录对应socket实例的使用信息

  connections:WebSocket[] = [] // 历史实例，用于避免多次连接、多次触发相关方法
  aliveIntervalList:Array<ReturnType<typeof setInterval>> = [] // 历史实例，用于避免多次连接、多次触发相关方法

  timeoutTimerList:Array<ReturnType<typeof setTimeout>> = [] // 历史 setTimeout

  headers:undefined | HeadersInit // 交易中心的请求信息
  accessToken:string | undefined // 登录etrm系统返回的token

  aliveIntervalTime:number = 25000 //心跳间隔毫秒
  reconnectTime:number = 8000 // 重连间隔毫秒

  checkDelay:number = delayConfig.checkDelay
  sellBuyDelay:number = delayConfig.sellBuyDelay

  messageQueue = new AsyncQueue<ZpSocketParams>()

  constructor(socketName:string) {
    this.socketName = socketName
    this.bindThis()

    this.startProcessing()
  }

  /** 把方法绑定到实例 */
  private bindThis () {
    const thisFnList = <const>['initSocket', 'initSocketThrottle', 'onopen', 'onopenThrottle']
    thisFnList.forEach((key: typeof thisFnList[number]) => {
      this[key] = this[key].bind(this) as any
    })
  }

  bindEvt = (evt:keyof WebSocketEventMap, listener: (this: WebSocket, ev: CloseEvent | Event | MessageEvent<any>) => any) => {
    UniqueSocket.ws.addEventListener(evt, listener)
  }

  bindAllEvt = () => {
    this.bindEvt('open', this.onopenThrottle)
    // 此处不绑定onmessage事件，onopen事件中删除历史soket后再绑定
    // this.bindEvt('message', this.onmessage)
    this.bindEvt('close', this.onclose)
    this.bindEvt('error', this.onmerror)
  }

  clearTimeoutTimer = () => {
    this.timeoutTimerList.forEach(timer => clearTimeout(timer))
    this.timeoutTimerList.length = 0
  }

  /** 保存 headers、kpid */
  setParamsInfo = async () => {
    try {
      const {headers} = await getSpotMartetZcqReqInfo()
      this.headers = headers
    }catch(e) {
      console.error(e)
      return Promise.resolve()
    }
  }

  /** 初始化方法 节流 */
  public initSocketThrottle = throttle(this.initSocket, 5000, {trailing:true, leading: false})

  reconnect = () => {
    if (UniqueSocket?.ws?.readyState !== WebSocket.OPEN) {
      this.initSocketThrottle()
    }
  }

  /** 初始化 */
  private async initSocket(api:string = ZgpSocketUrl){
    try {
      console.log(`initSocket ${this.socketName}`)
      this.clearSocket()
      this.clearTimeoutTimer()
  
      await this.setParamsInfo()
      
      UniqueSocket.api = api || UniqueSocket.api
      if(!UniqueSocket.api){
        console.error(`UniqueSocket:{ ${this.socketName} } 初始化失败！ socket连接地址不存在`)
        return Promise.resolve()
      }
      // this.clearSocket()
      const socketUrl = await this.generalSocketUrl()
      console.log(`socketUrl ${this.socketName}`, socketUrl)
      if(!socketUrl){
        console.error(`${this.socketName}:socketUrl 初始化失败！ socket连接地址不存在`)
        this.clearTimeoutTimer()
        const timer = setTimeout(this.reconnect, this.reconnectTime)
        this.timeoutTimerList.push(timer)
        return Promise.resolve()
      }
      UniqueSocket.ws = new WebSocket(socketUrl)
      this.connections.push(UniqueSocket.ws)
      console.log('this.connections.length', this.connections.length)
      this.bindAllEvt()
  
      return Promise.resolve(UniqueSocket.ws)
    } catch(e) {
      console.error(e)
      return Promise.resolve()
    }
  }

  /** 处理socket的url */
  generalSocketUrl = async () => {
    try {
      this.accessToken = undefined
      const res = await yunNao.login({
        encryptData: encrypt(yunNaoAccount),
        encryptLogin:true
      })
      this.accessToken = res?.access_token || ''
      return Promise.resolve(`${UniqueSocket.api}&accessToken=${this.accessToken}&thirdId=${userUnitid[priviateAccount]}`)
    }catch(e) {
      console.error(e)
      return Promise.resolve()
    }
  }

  /** 清除所有socket连接实例，确保只有一个连接，只能触发一次相关实例回调 */
  clearSocket = () => {
    this.headers = undefined
    this.accessToken = undefined

    this.clearSocketKeepAlive()

    // CONNECTING：值为0，表示正在连接。
    // OPEN：值为1，表示连接成功，可以通信了。
    // CLOSING：值为2，表示连接正在关闭。
    // CLOSED：值为3，表示连接已经关闭，或者打开连接失败。
    this.connections.forEach(ws => {
      console.log('clearSocket', ws)
      if (WebSocket.OPEN === ws.readyState || WebSocket.CONNECTING === ws.readyState) {
          this.close(ws);
      }
    })

    this.connections = []
  }

  /** 删除历史socket实例，保证只有一个soket连接 */
  clearHistorySocket = () => {
    if(this.connections.length <= 1) return

    const clearAliveIntervals = this.aliveIntervalList.slice(0, -1)
    this.aliveIntervalList = this.aliveIntervalList.slice(-1) 
    clearAliveIntervals.forEach(aliveInterval => clearInterval(aliveInterval))

    const clearSockets = this.connections.slice(0, -1)
    this.connections = this.connections.slice(-1) 
    clearSockets.forEach(ws => {
      console.log('clearHistorySocket', ws)
      if (WebSocket.OPEN === ws.readyState || WebSocket.CONNECTING === ws.readyState) {
        this.close(ws);
      }
    })
  }

  /** 发送到服务端 */
  sendMsg = (msg:any) => {
    if(UniqueSocket.ws.readyState === WebSocket.OPEN) {
      const sendMessage = (msg === 'ping' || msg === 'pong') ? msg : JSON.stringify(msg)
      UniqueSocket.ws.send(sendMessage)
      return
    }

    this.initSocketThrottle()
    console.error(`${this.socketName}socket连接错误，无法发送消息到服务端`)
  }

  /** 处理交易下单结果数据 */
  generateSendData = ({type, data}: {type:ZpBackParams["type"],data:Partial<ZpBackParams["data"]>}):ZpBackParams => {
    return {
      type: type,
      data: {
        backParams: null,
        zpdlReal: data?.zpdlReal || null,
        "msg": '',
        "code": 0,
        ...data
      }
    }
  }

    // 启动队列处理
    startProcessing = async (): Promise<void> => {
      for await (const message of this.messageQueue) {
          try {
              const sendMsg = await this.zpTrade(message)
              this.sendMsg(sendMsg)
          } catch (error) {
              console.error('Error processing message:', error);
          }
      }
    }
  
  /** 买卖的摘牌 */
  zpTrade = async ({type, data}:ZpSocketParams) => {
    try {
      if(!this.headers || !this.accessToken) {
        console.error('this.headers this.accessToken 失败')
        await this.initSocket()

        if(!this.headers || !this.accessToken) return Promise.resolve(this.generateSendData({type, data:{"code": 0,"msg": "获取请求信息失败！", backParams:data}}))
      }
      // 获取滑块验证吗校验结果
      let val = await new ImageVerification().getFinalCheckResult()
      if(!val) {
        await promisetTimeOut(2000)
        val = await new ImageVerification().getFinalCheckResult()
      }
      if(!val) {
        console.error('getFinalCheckResult 验证失败！')
        return Promise.resolve(this.generateSendData({type, data:{"code": 0,"msg": "getFinalCheckResult验证失败！", backParams:data}}))
      }
      let zpdlReal = data.zpdl
      const theDetail = await getOneGpxxData({headers:this.headers, gpid:data.gpid})
      if(Number(data.zpdl) > Number(theDetail.sydl)) {
        zpdlReal = theDetail.sydl
      }
      // 处理 过滤服务端发送过来的下单参数
      const paramsDealed:ZpParams = {
        gpid: String(data.gpid),
        bfcj: String(data.bfcj) as '0' | '1',
        zpdl: String(zpdlReal),
        jydyid: userUnitid[priviateAccount],
        val
      }

      // 下单参数
      const formParams = new URLSearchParams(paramsDealed)

      await promisetTimeOut(this.sellBuyDelay)

      // 交易中心下单
      const dataObj = await requestSDspotMarketZcq(
        `https://pmos.sd.sgcc.com.cn:18080/zcq/scgpjy/jysb.do?method=zpsb`,
        formParams,
        { method:'POST', headers: this.headers }
      )

      if(getDataType(dataObj) === 'object') return Promise.resolve(this.generateSendData({type, data:{...dataObj, backParams:data, zpdlReal}}))
      if(getDataType(dataObj) === 'string') return Promise.resolve(this.generateSendData({type, data:{"code": 0,"msg": dataObj, backParams:data, zpdlReal}}))
    }catch(e) {
      return Promise.resolve(this.generateSendData({type, data:{"code": 0,"msg": "服务异常！", backParams:data}}))
    }
  }

  /** 保持心跳连接发送的数据 */
  sendPing = () => {
    if(UniqueSocket.ws.readyState === WebSocket.OPEN) return this.sendMsg('ping')
    
    this.initSocketThrottle()
  }

  /** 保存历史socket的interval */
  socketKeepAlive = () => {
    const aliveInterval = setInterval(this.sendPing, this.aliveIntervalTime)
    this.aliveIntervalList.push(aliveInterval)
  }

  /** 清除历史socket的interval */
  clearSocketKeepAlive = () => {
    this.aliveIntervalList.forEach(aliveInterval => clearInterval(aliveInterval))
    this.aliveIntervalList.length = 0
  }

  /** socket 实例的回调 节流*/
  onopenThrottle = throttle(this.onopen, 1000, {trailing:true, leading: false})

  /** socket 实例的回调 */
  onopen(evt:any){
    console.log(`${this.socketName} open`, evt)
    this.socketKeepAlive()

    // 此处保证再次保证只有一个连接后，再绑定事件，再一重避免重复下单
    this.clearHistorySocket()
    this.bindEvt('message', this.onmessage)
  }

  /** 校验服务端发过来的下单参数 */
  validateParams = (params:ZpSocketParams) => {
    if(!params || !params.type || !params.data) return {pass:false, msg:'交易参数校验失败！'}

    const {type, data} = params
    if(type !== 'zp') return {pass:false, msg:'参数type校验失败！'}
    if(getDataType(data) !== 'object') return {pass:false, msg:'参数data校验失败！'}
    if(!(hasValue(data.gpid) && hasValue(data.zpdl) && hasValue(data.bfcj))) return {pass:false, msg:'参数data对象校验失败！'}

    return {pass:true, msg:''}
  }

  /** socket 实例的回调 */
  onmessage = async (evt:any) => {
    try{
      console.log("onmessage", evt);
      const evtData: | 'ping' | 'pong' = evt.data
      if(evtData === 'ping' || evtData === 'pong') {
        if(evtData === 'ping') return this.sendMsg('pong')
        console.log('onmessage 心跳检测连接', evtData)
        return
      }

      const {result, jsonData = ''} = validateAndParseJSON(evtData)

      if(!result || !jsonData) {
        console.error('validateAndParseJSON 参数校验不通过')
        return this.sendMsg(this.generateSendData({type:'', data:{"code": 0,"msg": 'JSON 数据为空或解析错误', backParams: null}}))
      }

      const receiveMsg:ZpSocketParams = jsonData

      const {pass,msg} = this.validateParams(receiveMsg)
      if(!pass) {
        console.error('validateParams 参数校验不通过')
        return this.sendMsg(this.generateSendData({type: (receiveMsg && receiveMsg?.type) || '', data:{"code": 0,"msg": msg, backParams: (receiveMsg && receiveMsg?.data) || null}}))
      }
      if(receiveMsg.type === 'zp') {
        // 将消息加入队列
        this.messageQueue.enqueue(receiveMsg as ZpSocketParams);
      }

    }catch(e) {
      console.error(e)
      this.sendMsg(this.generateSendData({type:'', data:{"code": 0,"msg": "交易前处理异常！",backParams:null}}))
    }
  }
 
  /** socket 实例的回调 */
  onclose = (evt:any) => {
    console.log("onclose", evt);

    // 非主动手动关闭时 重连
    if(evt.reason !== 'active_manual') {
      this.clearTimeoutTimer()
      const timer = setTimeout(this.reconnect, this.reconnectTime)
      this.timeoutTimerList.push(timer)
    }
  }
  
  /** socket 实例的回调 */
  onmerror = (evt:any) => {
    console.log("onmerror", evt);
  }
  
  /** 主动手动 关闭socket连接 */
  close = (ws?:WebSocket) => {
    try {
      (ws || UniqueSocket.ws)?.close(3001, 'active_manual')
    }catch(e) {
      console.error(e)
    }
  }
}

export const ZgpSocketIntance = new UniqueSocket('摘挂牌')